#!/usr/bin/env python


"""

entwinelib.py
=============

Desc: Library for querying twine.com

Author: Rob Cakebread <cakebread a t gmail .com>

License  : New BSD (See COPYING)

"""

__docformat__ = 'restructuredtext'

import os
import urllib2
import logging
from urlparse import urlparse

from rdflib import ConjunctiveGraph, Namespace
import workerpool

from entwine.utils import (get_user_passwd, get_toplevel_path,
        get_base_path_url)


PROFILE_URL = 'http://www.twine.com/user'
ITEM_URL = 'http://www.twine.com/item'

XMLNS = Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
APP = Namespace('http://www.radarnetworks.com/shazam#')
BASIC = Namespace('http://www.radarnetworks.com/2007/09/12/basic#')
RADAR = Namespace('http://www.radarnetworks.com/core#')
WEB = Namespace('http://www.radarnetworks.com/web#')



class TwineConnection():

    """
    Defines a connection to the Twine server
    """

    def __init__(self):
        #Used for authentication to keep track of where we can go
        self.opener = {}
        self.logger = logging.getLogger("entwine")

    def connect(self, url):
        """
        Gets connection to Twine server using authentication and sends header
        info using 'Accept application/rdf+xml' so we can fetch a Twine Item
        
        @param: url
        @type: string

        @returns: string RDF/XML
        
        """
        toplevel = get_toplevel_path(url)
        password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
        username, password = get_user_passwd()
        password_mgr.add_password(None, toplevel, username, password)
        handler = urllib2.HTTPBasicAuthHandler(password_mgr)
        self.opener[toplevel] = urllib2.build_opener(handler)
        self.opener[toplevel].addheaders = [('Accept', 'application/rdf+xml'), 
            ('Content-Type',  'application/rdf+xml'), 
            ('User-agent', 'entwine: http://code.google.com/p/entwine)')]
        self.logger.debug(url)

    def get(self, url):
        """
        Fetch a Twine Item, using authentication

        @param: url
        @type: string

        @returns: string RDF/XML
        
        """
        toplevel = get_toplevel_path(url)
        if self.opener.has_key(toplevel):
            #No need to re-authenticate if we've been in this level of site
            return self.opener[toplevel].open(url)
        else:
            #Authenticate, then fetch
            self.logger.debug("AUTH: Authenticating %s" % toplevel)
            self.connect(url)
            return self.opener[toplevel].open(url)


class TwineItem():

    """
    Class containing methods for manipulating Twine Items

    """

    def __init__(self, connection=None):
        #Item Ids for each person this person hasRelationshipTo
        self.relationships = []
        #Item Id for the person we're talking about
        self.is_person = None
        #Item RDF/XML string for the person we're talking about
        self.item_xml = None
        self.logger = logging.getLogger("entwine")
        if not connection:
            self.connection = TwineConnection()
        else:
            self.connection = connection

    def get_item_by_url(self, item_url):
        """
        Fetch Twine Item
        
        @param: item_url
        @type: string
        
        @returns: RDF/XML
        @rtype: string

        """
        return self.connection.get(item_url)


    def get_triple_object(self, item_xml, namespace, predicate):
        """
        Return object of a triple given item_xml

        @param: item_xml
        @type: string

        @param: namespace
        @type: rdflib Namespace

        @param: predicate
        @type: string

        @return: string of a triple object

        Example: get_triple_object(item_xml, RADAR, 'isPerson')

        """
        store = ConjunctiveGraph()
        store.parse(item_xml, publicID=None, format="xml")
        for subj, obj in store.subject_objects(namespace[predicate]):
            self.logger.debug(subj)
            self.logger.debug(predicate + u': %s' % obj)
            #We should yield this... so we can replace get_relationships
            #Actually we should use rdfalchemy!
            return obj

    def show(self, item_id):
        """
        Print Item to stdout

        @param item_id: Twine Item id string
        @type: string
        @returns: RDF XML string

        """
        print self.get_item_by_url("%s/%s" % (ITEM_URL, item_id)).read()


    def get_item(self, item_id):
        """
        Fetch an Item from a Twine

        @param item_id: Twine Item id string
        @type: string
        @returns: file descriptor for RDF/XML string

        """
        return self.get_item_by_url("%s/%s" % (ITEM_URL, item_id))

class WorkerJob(workerpool.Job):

    def __init__(self, item_id, verbose, con):
        self.item_id = item_id
        self.verbose = verbose
        self.con = con

    def run(self):
        username = self.con.get_username_from_item_id(self.item_id)
        item_xml = self.con.get_item(self.item_id)
        first_name = self.con.get_triple_object(item_xml, BASIC, 'firstName')
        item_xml = self.con.get_item(self.item_id)
        last_name = self.con.get_triple_object(item_xml, BASIC, 'lastName')
        if self.verbose:
            print self.item_id,
        print "%s %s - %s" % (first_name, last_name, username)

class TwineProfile(TwineItem):

    """
    Methods for fetching and manipulating a Twine User Profile
    which is a specific type of Twine Item

    """

    def __init__(self, item=None):
        TwineItem.__init__(self)
        self.item = item

    def get_profile(self, username):
        """
        Get RDF for a given username
        
        @param: username
        @type: string
        @returns: string of RDFL XML
        
        """
        return self.get_item_by_url("%s/%s" % (PROFILE_URL, username))

    def parse_profile(self, profile):
        """
        Parse RDF profile into triples and print them out.
        @param profile: RDF XML of a person's profile
        """
        
        store = ConjunctiveGraph()
        store.parse(profile, publicID=None, format="xml")
        self.print_profile_section(store, APP,
                ['status', 'wasInvitedBy', 'location'])
        self.print_profile_section(store, BASIC, ['url'])
        self.print_profile_section(store, RADAR, \
                ['isPerson', 'createdDate', 'lastModifiedDate'])
        self.print_profile_section(store, WEB, ['views'])
        self.logger.debug("Fetching RDF for isPerson Item id %s..." % \
                self.is_person)
        self.print_person_item(self.get_item(self.is_person))

    def get_relationships(self, username):
        """
        Return list of relationship Items for Username
        @param: username
        @type: string
        @return: list of Item Ids

        """
        self.is_person = self.get_username_item(username)
        if self.is_person is None:
            self.logger.error("Error: Couldn't get IsPerson")
            return None

        person_item = self.get_item(self.is_person)
        store = ConjunctiveGraph()
        store.parse(person_item, publicID=None, format="xml")
        predicate = 'hasRelationshipTo'
        for subj, obj in store.subject_objects(BASIC[predicate]):
            self.logger.debug(subj)
            self.logger.debug(predicate + u': %s' % obj)
            self.relationships.append(os.path.basename(urlparse(obj).path))
        return self.relationships

    def show_relationships(self, username, verbose):
        """
        Show relashonships for a user
        
        @param: username
        @type: string

        """
        self.get_relationships(username)
        for item_id in self.relationships:
            if verbose:
                print "%s/%s" % (ITEM_URL, item_id)
            else:
                print item_id

    def get_username_item(self, username):
        """
        Get the Item Id for a username

        @param: username
        @type: string
        @return: string of Twine Item Id
        """
        profile = self.get_profile(username)
        store = ConjunctiveGraph()
        store.parse(profile, publicID=None, format="xml")
        #First we need to find isPerson
        predicate = 'isPerson'
        for subj, obj in store.subject_objects(RADAR[predicate]):
            self.logger.debug(subj)
            self.logger.debug(predicate + u': %s' % obj)
            #Get the Item Id of this person so we can fetch that item
            #obj is entire URL to item, we just want the item id itself:
            return os.path.basename(urlparse(obj).path)

    def get_username_from_item_id(self, item_id):
        """
        Get username from an Item's Id
        
        @param: item_id
        @type: string
        @return: string of username
        
        """
        item_xml = self.get_item(item_id)
        #This gets you http://www.twine.com/user/<username>:
        is_user = self.get_triple_object(item_xml, RADAR, 'isUser')
        #We return just the username part:
        return get_base_path_url(is_user)

    def print_person_item(self, person):
        """
        Print a Person's Item
        
        @param person: string of RDF XML
        @returns: None
        
        """
        store = ConjunctiveGraph()
        store.parse(person, publicID=None, format="xml")
        self.print_profile_section(store, BASIC, ['lastName', 'firstName',
            'description', 'hasRelationshipTo'])

    def print_profile_section(self, store, namespace, elements):
        """Print the triples"""
        for predicate in elements:
            for subj, obj in store.subject_objects(namespace[predicate]):
                self.logger.debug(subj)
                print predicate + ': %s' % obj
                if predicate == 'isPerson':
                    #Get the Item Id of this person so we can fetch that item
                    #obj is entire URL to item, we just want the item id itself:
                    self.is_person = os.path.basename(urlparse(obj).path)
                elif predicate == 'hasRelationshipTo':
                    self.relationships.append(get_base_path_url(obj))

    def show_verbose_relationships(self, username, verbose):
        """
        Show relashonships for a user
        
        @param: username
        @type: string

        """
        pool = workerpool.WorkerPool(size=7)
        self.get_relationships(username)
        for item_id in self.relationships:
            job = WorkerJob(item_id, verbose, self)
            pool.put(job)
        pool.shutdown()
        pool.join()
        del pool
        #What the...
        from time import sleep
        sleep(.01)

def twine_test():
    """Quick and dirty test."""
    item = TwineItem()
    print item.get_item("1tx52n3d-gv").read()
    profile = TwineProfile()
    profile.get_profile("cakebread").read()
    profile.parse_profile(profile.get_profile("cakebread"))
    profile.get_relationships("cakebread")


if __name__ == "__main__":
    twine_test()
